Skip to content

feat(evm): opt-in short-circuit for future blocks without served-tip - #33

Open
shpookas wants to merge 23 commits into
mainfrom
fix/future-block-short-circuit
Open

shpookas wants to merge 23 commits into
mainfrom
fix/future-block-short-circuit

Conversation

@shpookas

@shpookas shpookas commented Oct 9, 2026 •

Copy link
Copy Markdown

Based on main after syncing with erpc/erpc main (WebSocket support merged). The branch has latest main merged in; the diff is only this change.

Problem

Clients that follow a chain by number (for example rollup nodes reading L1) poll eth_getBlockByNumber(head+1) before the block exists and treat null as "not yet". No upstream has the block, so each upstream returns null in turn. The request walks every routed upstream, fallbacks included, and the retry layer repeats the walk. On a network that routes all tiers in one list, almost all fallback traffic ends up being these nulls.

tryShortCircuitFutureBlock already returns null from Forward before any dispatch when the block is above every upstream's head. But it only runs on networks that enable served-tip for latest, so networks without it (the default) still fan out. Enabling served-tip to get it also changes what latest resolves to.

Change

New network option evm.shortCircuitFutureBlocks (default off, inheritable from networkDefaults.evm). The short-circuit runs when it is true or when served-tip is enabled for latest, so nothing changes for anyone who does not set it. latest is unchanged.

The short-circuit compares the requested block against futureBlockCeiling: the highest effective latest head (static caps included) across every upstream of the network. It returns null only when the block is above that, and dispatches as usual while any upstream's head is unknown. It deliberately does not try to predict routing:

  • selection policy is ignored, so a fallback the policy cordons off but the per-request escape can still reach counts, as do method-scoped policy slots
  • syncing upstreams count, whatever skipWhenSyncing says
  • heads are always latest, also with emptyResultConfidence: finalizedBlock. That setting decides how an empty upstream answer is treated, not whether an upstream can have the block

So it never nulls a block any upstream reports having. The cost is that it fires less often: an upstream whose head is never learned (for example a broken endpoint) keeps it off for that network, which is today's behaviour.

The short-circuit now also runs before Forward publishes the request to the selection policy's probe bus. Before, probeExcluded still mirrored requests that were answered with the synthetic null to the excluded upstreams (28 mirrored calls for 5 short-circuited requests in the test below). The network pre-forward hook it now runs ahead of does not handle eth_getBlockByNumber, so nothing else changes order.

This also changes the short-circuit for networks that already enable served-tip for latest: it used evmHeadReference(...).Available (policy-eligible, non-syncing upstreams, finalized axis under finalizedBlock confidence). The new ceiling is never lower, so it only ever dispatches more.

Why opt-in and not just dropping the served-tip gate

The first version of this PR removed the gate. That breaks the issue 934 tests (TestNetworkAvailability_Issue934_*, TestNetworkAvailability_Window_ExactLowerUpper): the head is what eRPC last saw from the state poller and head subscriptions, so a block the node has produced since then (and a client already saw via the node's own newHeads) gets null instead of being served. That is acceptable where heads arrive promptly over WebSocket and the clients retry a null, but not as a default for every network. The docs say so.

Files

  • common/config.go: EvmNetworkConfig.ShortCircuitFutureBlocks and ShortCircuitFutureBlocksEnabled()
  • common/defaults.go: inherit from networkDefaults.evm
  • erpc/networks.go: gate on ShortCircuitFutureBlocksEnabled(), futureBlockCeiling, short-circuit before the probe-bus publish
  • typescript/config/src/generated.ts: shortCircuitFutureBlocks?: boolean
  • docs/pages/config/projects/networks.mdx: "Future-block polling" section; agent reference schema row, networkDefaults inheritance list, edge cases 23, 24 and 31, and the future_block.short_circuit span attribute

Tests

erpc/networks_future_block_test.go (existing tests unchanged). With the option on and served-tip off:

  • OptInWithoutServedTip_ShortCircuitsToNull: a block above every head returns null and no upstream receives the request
  • OptInWithoutServedTip_OnlyMostAheadHasBlock_Dispatches: a block above the corroborated head that the most-ahead upstream has is dispatched
  • OptIn_SyncingUpstreamAhead_Dispatches: a syncing upstream ahead of the others means the block is dispatched
  • OptIn_UpstreamWithUnknownHead_Dispatches: one upstream with no known head means the block is dispatched
  • OptIn_CordonedFallbackAhead_Dispatches: primaries at 100, fallbacks at 110 cordoned by the default policy with failover enabled, block 105 is dispatched
  • OptIn_FinalizedConfidence_UnfinalizedBlock_Dispatches: emptyResultConfidence: finalizedBlock, finalized 90, latest 98-100, block 95 is dispatched
  • OptIn_ShortCircuitNotMirroredToExcluded: default policy with cordoned fallbacks, 5 short-circuited requests reach no upstream, mirrored probes included. Fails 3/3 with the old order

The syncing, unknown-head and cordoned-fallback tests fail with the previous Available ceiling; the finalized one fails with the finalized-axis comparison. The forwarding tests register their mocks before network setup and match only the requested block number, so the state poller's polls never hit them.

common/config_test.go:

  • TestNetworkConfig_SetDefaults_ShortCircuitFutureBlocksInheritsFromDefaults: a true default is inherited, and an explicit network-level false overrides it

  • TestEvmNetworkConfig_ShortCircuitFutureBlocksEnabled: nil, unset, false, true, and served-tip for latest vs finalized only

  • go build ./..., go vet ./erpc (only the existing query_executor_test.go:341 warning)

  • go test ./common/ ./architecture/evm/ pass

  • go test ./erpc/ -timeout 40m on the merged branch: everything passes except two tests that also fail on plain erpc/erpc main (3/3 runs each):

    • TestHttpServer_H2C_ShutdownGoAwaysAndDrainsInFlightStream
    • TestNetwork_Forward/ForwardLlamaRPCEndpointRateLimitResponseSingle

jleeh and others added 15 commits October 7, 2026 17:51
- util/initializer: bound the auto-retry wait, reap tasks that overrun
  their timeout, and make an attempt's start and finish atomic so a
  reaped attempt can never overwrite the next one's state. Wait never
  reports a failed task as succeeded, and an attempt that finds the app
  context cancelled still releases the WaitGroup ExecuteTasks holds
  tasksMu for.
- util/gzip_pool: closing a pooled reader also closes its source body.
- data: CounterInt64 pushes to remote only when the local value
  advanced and never overwrites a higher remote value; the keep-latest
  pubsub buffer keeps the fresher of two values. A refresh the caller's
  deadline allows past fallbackTimeout is still applied when it lands.
- common: UniqueUpstreamKey no longer includes the network id, which is
  assigned after the upstream is created and so changed the key over
  the upstream's lifetime.

Co-authored-by: shpookas <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- server.webSocket: buffer and message sizes, ping interval, per-
  connection subscription and in-flight request limits, and the per-
  subscription notification buffer; validated.
- indexer: dedupWindowSize (canonicalChainDepth is accepted and ignored).
- networks[].failover / networkDefaults.failover: onDefaultsExhausted,
  with a validation-report warning when no upstream is tagged
  tier:fallback.
- evm.stripSubscribeFromBlockZero, inherited from networkDefaults.
- Metrics for WebSocket connectivity, dropped subscription
  notifications and the fallback escape; generated TS types.

Co-authored-by: snowkide <[email protected]>
Co-authored-by: João Gomes <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ws:// and wss:// endpoints get a persistent client instead of the
"not implemented" stub. It serves regular requests and upstream
subscriptions:

- one read loop owns the connection lifecycle; re-dials wait a jittered
  exponential backoff (1s-30s) that resets only after a connection
  outlives the liveness window; ping/pong plus a read deadline detect
  half-open connections;
- requests fail fast with a retryable error while reconnecting and
  errors are normalised exactly like the HTTP client's;
- subscription handlers are registered before the next frame is read
  and are scoped to the connection that created them; eth_subscribe and
  eth_unsubscribe are only sent on behalf of a subscription owner.

Upstream.IsDown checks the breaker of the policy serving a method, and
the registry exposes the WebSocket and fallback-escape upstream sets.

A response the read loop already claimed reaches its caller even if the
caller's context expired meanwhile, so a subscribe that completed is
never left without an owner; a notification for a subscription no
handler owns triggers eth_unsubscribe, which also retries a cancel that
failed. A panic in a subscription handler or callback is recovered per
frame. jsonRpc.proxyPool is refused for ws/wss upstreams, whose dialer
has no proxy support.

Co-authored-by: snowkide <[email protected]>
Co-authored-by: João Gomes <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- enforceHighestBlock also applies to cache hits and keeps the caller's
  use-upstream selector; when the re-fetch misses it still serves the
  highest valid block.
- Cached latest/finalized entries are keyed by the block the tag
  currently resolves to, so each tip advance is a new key.
- Only the observed tx_replay_attack token is treated as already known.

Co-authored-by: shpookas <[email protected]>
Co-authored-by: João Gomes <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Delivered-head floor: a head already delivered on a WebSocket
  subscription, from an upstream eligible to serve the network, floors
  "latest" so HTTP never regresses below it. It is bounded against live
  heads, per project, skipped for use-upstream requests, never lifts
  past servedTip guarantees, and shared across instances in the
  background.
- failover.onDefaultsExhausted: when every routed upstream failed with
  an error retryable toward the network, retry the request once on
  tier:fallback upstreams not yet tried, before the selection policy
  promotes them. Empty results count only for a block that should
  exist; consensus is excluded. The escape honours a use-upstream pin,
  and keeps the routed upstreams' empty answer if every fallback fails.
- Near-tip eth_getBlockByNumber prefers an upstream strictly ahead of
  the others; requests for a specific block try upstreams that have it
  first, preserving score order.
- Non-retryable writes are not multiplexed.

Co-authored-by: shpookas <[email protected]>
Co-authored-by: João Gomes <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The indexer sits between event ingresses and egresses:

- ingresses push StreamEvents; newHeads from every source update that
  upstream's poller before dedup, then heads are delivered once and in
  order per network;
- filter subscriptions (logs, newPendingTransactions) are refcounted
  per params key so clients share one upstream subscription per
  upstream, with subscribe and teardown serialised per filter;
- logs are deduplicated by blockHash/transactionHash/logIndex, with the
  upstream's own removed flag tracked per log so add, remove and re-add
  are each delivered once;
- adapters: wsupstream (WebSocket upstream ingress with resubscribe on
  reconnect), wsclient (per-connection egress with bounded per-
  subscription buffers) and nullingress (fixture ingress for tests).

Co-authored-by: snowkide <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HTTP and WebSocket share the port and URL paths. Over WebSocket:

- single and batch requests run through the same pipeline as HTTP:
  forward headers, method filters, auth, directives with
  allowClientDirectives, rate limits and server.maxTimeout, with a
  per-connection in-flight limit;
- eth_subscribe/eth_unsubscribe for newHeads, logs and
  newPendingTransactions via the indexer, with connection-scoped
  subscription ids and a per-connection subscription limit;
- a slow reader drops its own oldest newHeads/pending-tx notifications;
  a logs subscription that overflows closes the connection with 1013;
- peers silent for two ping intervals are dropped; on shutdown clients
  are closed with 1001 immediately while in-flight requests finish.

Only a real RFC 6455 handshake bypasses gzip and maxTimeout. The
method-not-allowed response now echoes the request id.

Every message authenticates with the upgrade request's credentials, so
an upgrade no auth strategy accepts is refused with 401; method scoping
and rate limits still apply per message. A valid notification (no id)
runs without a reply, alone or in a batch. A request that panics gets
an error carrying its own id, and errors raised before network
resolution use the URL's architecture for their wire code.

Co-authored-by: snowkide <[email protected]>
Co-authored-by: shpookas <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Clients receive each head from whichever WebSocket upstream delivers it
first, eligible to the selection policy or not. Feeding the floor only
from eligible upstreams let HTTP "latest" sit a block behind a head a
subscriber had just received whenever the fastest upstream was excluded
— which a lag-based policy does routinely. Any known source whose poller
accepted the head now feeds it; the floor stays bounded against live
heads.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
On fast chains the upstream that answered "latest" one block behind is
often the one whose own feed just delivered the tip, and so the only
upstream that already has it. Excluding it made the re-fetch return
null from every other upstream and fail open to the stale block. A
corrupted tip is still caught: every upstream returns null for it and
pickHighestBlock keeps the served block.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
gosec G404 flags math/rand in the reconnect backoff. The jitter only
spreads redials; annotate it like the repo's other jitter uses.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The hedge keeper kept an ErrUpstreamsExhausted whose causes were all
missing-data, on the premise that no sibling leg could do better. A
sibling may be on an upstream this leg never tried, e.g. a leg the
fallback escape sent to a slow fallback while another leg re-swept the
primaries, so the fast miss cancelled the leg about to succeed. Such a
result now keeps the race going; if every leg ends that way the hedge
still returns the last one to the retry layer.

Co-authored-by: Vitas Spokas <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
newHeads is subscribed on every WebSocket upstream and each head goes
out from whichever source announces it first. With failover on, a
fallback-tier upstream whose WebSocket is faster told clients about a
block no primary had yet; their follow-up reads then missed on the
primaries, or escaped to the fallback while the primaries were healthy.

A fallback's head now reaches clients, and feeds the delivered-head
floor, only while no upstream outside that tier which the selection
policy routes to has a live newHeads subscription of its own. "Down"
is the policy's verdict, as for reads, so custom policies that keep
fallbacks in the ordered list behave the same. A network whose only
WebSocket upstreams are fallbacks, or whose primaries' subscriptions
are dead, keeps receiving fallback heads. A held-back head still
updates that fallback's state poller.

NetworkHandle.SuggestLatestBlock now reports whether the head may be
delivered; the indexer drops it before dedup otherwise.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
logs and newPendingTransactions picked their upstreams once, when the
first client subscribed: on every primary whose eth_subscribe breaker
was closed, on the fallbacks only if every primary rejected the
subscribe. Nothing revisited that for the filter's lifetime, so a
filter went silent when its primaries dropped it, stayed on the
fallbacks after the primaries recovered, and never reached a primary
whose first subscribe failed.

A filter now belongs on every primary the selection policy routes to,
and on the fallback tier while none of those has it live; the indexer
rechecks this on every head, adding and removing the filter as that
changes. The fallbacks hand a filter back only once a primary has it
live, so there is no gap. An upstream whose subscribe fails keeps the
filter and retries it in the background, like its other subscriptions.

Only heads a tip tracker has accepted reach subscribers: a major jump is
verified before it is delivered, and a source with no tracker is
withheld, so an unverified head can't advance the indexer's dedup
marker. The upstream eth_unsubscribe outlives a caller that already
gave up but keeps a live caller deadline.

The indexer and the subscription manager key a network by project and
network, so two projects that define the same network keep their own
upstreams. A filter subscribe is bounded by the attempt timeout, since
callers hold locks across it and a dropped reply on a busy connection
would otherwise never time out.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The short-circuit can return null despite a routable syncing or fallback upstream having the requested block.

1 open finding
What changed in this PR

Enables future-block short-circuiting when served-tip is disabled.

Changes:

  • Removes the served-tip eligibility gate.
  • Updates tests for default-mode short-circuiting and ahead-upstream dispatch.
File Description
erpc/​networks.go Enables short-circuiting for all EVM networks.
erpc/​networks_future_block_test.go Tests disabled served-tip behavior.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread erpc/networks.go Outdated
tryShortCircuitFutureBlock returns null for a numbered eth_getBlockByNumber
above the highest head any eligible upstream has (static caps included), so
it never nulls a block an upstream has. It only runs when served-tip is
enabled for latest.

Clients that poll the next block by number get a null from each upstream in
turn on networks without served-tip, so every poll walks every routed
upstream, fallbacks included, and the retry layer repeats the walk.

Add evm.shortCircuitFutureBlocks to enable the short-circuit on its own,
without changing what latest resolves to. It is off by default: the head is
what eRPC last saw, so a block produced since then can briefly get null
(the issue 934 case), which only suits networks whose heads arrive promptly.
@shpookas
shpookas force-pushed the fix/future-block-short-circuit branch from d93186c to b5a30a3 Compare October 9, 2026 09:40
@shpookas shpookas changed the title fix(network): short-circuit future blocks without served-tip feat(evm): opt-in short-circuit for future blocks without served-tip Oct 9, 2026
@shpookas
shpookas requested a balanced review from Copilot October 9, 2026 09:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Documentation completeness, inheritance coverage, and deterministic gock setup need correction.

5 open findings
1 resolved since last review

🧠 Review effort: Balanced

Comment thread common/defaults.go
Comment thread erpc/networks_future_block_test.go Outdated
Comment thread common/config.go Outdated
Comment thread docs/pages/config/projects/networks.mdx Outdated
Comment thread docs/pages/config/projects/networks.mdx Outdated
The future-block short-circuit compares against the head reference, which
leaves syncing upstreams out. An upstream that reports syncing still receives
requests unless evm.skipWhenSyncing is set, so it may already have a block
above that reference. Dispatch when one of them is at or above the block.

Also:
- test networkDefaults inheritance and an explicit false override
- register gock mocks before network setup in the new forwarding tests
- docs: config schema row, edge cases, span attribute, comment style

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Finalized-block confidence can incorrectly short-circuit blocks already available at an upstream’s latest head.

2 open findings
4 resolved since last review

🧠 Review effort: Balanced

Comment thread erpc/networks.go Outdated
The future-block short-circuit used finalized heads when
emptyResultConfidence is finalizedBlock, so a block between an upstream's
finalized and latest head returned null without being dispatched.
emptyResultConfidence decides how an empty upstream answer is treated; it
should not decide whether an upstream can have the block. Always compare
against latest heads, including for dispatchable syncing upstreams.
@shpookas
shpookas requested a balanced review from Copilot October 9, 2026 11:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The short circuit can incorrectly return null when dispatchable upstream heads are unknown or omitted from wildcard policy candidates.

0 open findings

2 resolved since last review
Previously missed (2)

In code that hasn't changed since last review

Medium severity Fail open when any dispatchable upstream has an unknown head

erpc/​networks.go:940

Available only fails open when every candidate lacks a head; evmTipBallot silently drops each individual upstream whose poller/effective latest head is unknown. With one known upstream at 100 and another dispatchable upstream with an unknown head, block 101 is therefore synthesized as null even though the latter may already serve it. Before short-circuiting, fail open if any potentially dispatchable candidate has no positive latest-head observation, and add a mixed known/unknown regression case.

Medium severity Include fallback-escape upstreams when computing the request ceiling

erpc/​networks.go:943

This ceiling uses the wildcard selection-policy candidates rather than every upstream this request may dispatch to. The default policy excludes tier:fallback, but onDefaultsExhausted can later add those upstreams via GetFallbackEscapeUpstreams; thus primaries at head 100 plus a fallback at 110 will return an undispatched null for block 105 instead of letting the fallback serve it. Method-/boundary-scoped policy slots can similarly differ from the wildcard slot. Build the ceiling from the selected request upstreams plus eligible fallback-escape candidates.

🧠 Review effort: Balanced

The future-block ceiling came from the selection policy's wildcard
candidates plus a separate pass over syncing upstreams. That missed
upstreams a request can still reach: fallbacks the policy cordons off but
the per-request escape brings back, and method-scoped policy slots. It also
skipped upstreams with an unknown head, so a block one of them already had
returned null.

Compute the ceiling over every upstream of the network instead, regardless
of policy, tier or syncing state, and fail open while any upstream's head is
unknown. Replaces dispatchableSyncingHead.
@shpookas

shpookas commented Oct 9, 2026

Copy link
Copy Markdown
Author

Addressed both "previously missed" findings from the last review in 8d4b27d. Instead of mirroring routing (policy candidates per method, fallback escape, skipWhenSyncing), the ceiling is now the highest latest head across every upstream of the network, and the short-circuit fails open while any upstream's head is unknown:

  • unknown head: OptIn_UpstreamWithUnknownHead_Dispatches
  • fallback cordoned by policy but reachable through the escape: OptIn_CordonedFallbackAhead_Dispatches

Both fail with the previous Available ceiling. Trade-off: an upstream whose head is never learned keeps the short-circuit off for that network (documented in edge case 24).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Probe publication can still dispatch short-circuited requests to excluded upstreams.

2 open findings
Previously missed (1)

In code that hasn't changed since last review

Medium severity Probe dispatch bypasses future-block short-circuit

erpc/​networks.go:915

Forward publishes this request to the policy probe bus before calling this helper (erpc/networks.go:2173-2175). Because the default policy enables probeExcluded, an excluded upstream can still receive an asynchronous mirrored eth_getBlockByNumber even when this returns the synthetic null, contradicting the option's no-dispatch guarantee and preserving upstream load. Move probe publication after the future-block short-circuit, and cover an active excluded-upstream prober in the test.

🧠 Review effort: Balanced

Comment thread common/config.go Outdated
Comment thread docs/pages/config/projects/networks.mdx Outdated
Forward published each request to the selection policy's probe bus before
the future-block short-circuit, so probeExcluded still mirrored requests
that were answered with a synthetic null to excluded upstreams. Run the
short-circuit first. It only matches eth_getBlockByNumber, which the
network pre-forward hook does not handle, so nothing else moves.

Also describe the ceiling as every upstream of the network in the config
comment, TypeScript type and docs, and point edge case 24 at the
short-circuit code.
@shpookas

shpookas commented Oct 9, 2026

Copy link
Copy Markdown
Author

Addressed the last review in decea44:

  • probe dispatch: the short-circuit now runs before the probe-bus publish, so a synthetic null is never mirrored to excluded upstreams. OptIn_ShortCircuitNotMirroredToExcluded fails 3/3 with the old order (28 mirrored calls for 5 requests) and passes with the fix. The network pre-forward hook it now runs ahead of does not handle eth_getBlockByNumber.
  • "eligible" wording: the config comment, TypeScript type and docs snippets now say every upstream of the network, including that it never fires while any upstream's head is unknown.
  • edge case 24 now links to tryShortCircuitFutureBlock and futureBlockCeiling.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Safety documentation overstates guarantees despite the acknowledged stale-head behavior.

0 open findings

2 resolved since last review
Previously missed (2)

In code that hasn't changed since last review

Low severity Limit dispatch guarantee to latest observed effective heads

docs/​pages/​config/​projects/​networks.mdx:104

“A block any upstream has is still dispatched” contradicts the documented stale-head behavior below: an upstream may have produced the block after its last reported head, in which case this option returns null. Limit this guarantee to the latest observed effective heads.

Low severity Clarify safety claim to reference last observed effective heads

erpc/​networks.go:890

This safety claim is stronger than the implementation: the ceiling uses the last observed heads, so the function can return null for a block that an upstream already serves but whose poller/subscription has not reported it yet (the PR documents this stale-head case). Phrase this in terms of the last effective-head observations rather than actual upstream capability so the function contract does not contradict the opt-in caveat.

🧠 Review effort: Balanced

The short-circuit compares against the last heads eRPC observed, so it never nulls a block at or below a reported head, but a block produced after the last observed head can still get null. Say that in the tryShortCircuitFutureBlock comment and the docs instead of promising any block an upstream has is dispatched.
@shpookas

shpookas commented Oct 9, 2026

Copy link
Copy Markdown
Author

Addressed the two wording findings in dbecbcb: the tryShortCircuitFutureBlock comment and the docs now state the guarantee in terms of the last observed heads (never nulls a block at or below a head an upstream has reported), and say that a block produced after the last observed head can still get null, matching the stale-head caveat. Comment and docs only, no logic change.

@shpookas
shpookas changed the base branch from feat/websocket-support to main October 12, 2026 08:42
@shpookas
shpookas marked this pull request as ready for review October 12, 2026 09:09
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
File Lines Key changes Risk
🔵 config.go +25/-0 ShortCircuitFutureBlocksEnabled
🔵 networks.go +64/-40 futureBlockCeiling
🔵 networks.mdx +56/-3 —
2 test files +263

xray — see through AI slop with deterministic architecture PR diff reviews

The future-block short-circuit now uses futureBlockCeiling, so nothing reads Available. Remove it and update the evmHeadReference comment.

This branch was successfully deployed

1 active deployment
xray — 01dc553e Deployed Oct 12, 2026 by shpookas via xray #68
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants